/*
 * Copyright 2024 The WebRTC project authors. All rights reserved.
 *
 * Use of this source code is governed by a BSD-style license
 * that can be found in the LICENSE file in the root of the source
 * tree. An additional intellectual property rights grant can be found
 * in the file PATENTS.  All contributing project authors may
 * be found in the AUTHORS file in the root of the source tree.
 */

#include "video/corruption_detection/halton_frame_sampler.h"

#include <cstdint>
#include <cstring>
#include <memory>
#include <vector>

#include "api/array_view.h"
#include "api/scoped_refptr.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "video/corruption_detection/video_frame_sampler.h"

namespace webrtc {
namespace {

using ::testing::_;
using ::testing::AllOf;
using ::testing::DoubleEq;
using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Not;

using Coordinates = HaltonFrameSampler::Coordinates;

// Defaults for sampling tests.
constexpr int kDefaultScaledWidth = 4;
constexpr int kDefaultScaledHeight = 4;
constexpr double kDefaultStdDevGaussianBlur = 0.02;

#if GTEST_HAS_DEATH_TEST
// Defaults for blurring tests.
constexpr int kDefaultRow = 3;
constexpr int kDefaultColumn = 2;
constexpr double kDefaultStdDev = 1.12;
#endif  // GTEST_HAS_DEATH_TEST

VideoFrame MakeDefaultFrame() {
  // Create an I420 frame of size 4x4.
  const int kDefaultLumaWidth = 4;
  const int kDefaultLumaHeight = 4;
  const int kDefaultChromaWidth = 2;
  const uint8_t kDefaultYContent[16] = {20, 196, 250, 115, 139, 39, 99, 197,
                                        21, 166, 254, 28,  227, 54, 64, 46};
  const uint8_t kDefaultUContent[4] = {156, 203, 36, 128};
  const uint8_t kDefaultVContent[4] = {112, 2, 0, 24};

  return VideoFrame::Builder()
      .set_video_frame_buffer(I420Buffer::Copy(
          kDefaultLumaWidth, kDefaultLumaHeight, kDefaultYContent,
          kDefaultLumaWidth, kDefaultUContent, kDefaultChromaWidth,
          kDefaultVContent, kDefaultChromaWidth))
      .build();
}

VideoFrame MakeFrame(int width,
                     int height,
                     webrtc::ArrayView<const uint8_t> data) {
  scoped_refptr<I420Buffer> buffer = I420Buffer::Create(width, height);
  memcpy(buffer->MutableDataY(), data.data(), width * height);
  return VideoFrame::Builder().set_video_frame_buffer(buffer).build();
}

std::vector<Coordinates> MakeDefaultSampleCoordinates() {
  // Coordinates in all planes.
  return {{.row = 0.2, .column = 0.7},
          {.row = 0.5, .column = 0.9},
          {.row = 0.3, .column = 0.7},
          {.row = 0.8, .column = 0.4}};
}

TEST(GaussianFilteringTest, ShouldReturnFilteredValueWhenInputIsValid) {
  const int kWidth = 8;
  const int kHeight = 8;
  const uint8_t kData[kWidth * kHeight] = {
      219, 38,  75,  13,  77,  22,  108, 5,    //
      199, 105, 237, 3,   194, 63,  200, 95,   //
      116, 21,  224, 21,  79,  210, 138, 3,    //
      130, 156, 139, 176, 1,   134, 191, 61,   //
      123, 59,  34,  237, 223, 162, 113, 108,  //
      146, 210, 214, 110, 50,  205, 135, 18,   //
      51,  198, 63,  69,  70,  117, 180, 126,  //
      244, 250, 194, 195, 85,  24,  25,  224};
  // Chosing the point in the middle so all pixels are used.
  const int kRow = 3;
  const int kColumn = 3;
  // Resulting in a filter size of 3 pixels.
  const double kStdDev = 1;

  std::unique_ptr<VideoFrameSampler> sampler =
      VideoFrameSampler::Create(MakeFrame(kWidth, kHeight, kData));
  EXPECT_THAT(GetFilteredElement(*sampler, VideoFrameSampler::ChannelType::Y,
                                 kRow, kColumn, kStdDev),
              DoubleEq(126.45897447350468));
}

#if GTEST_HAS_DEATH_TEST
std::unique_ptr<VideoFrameSampler> MakeDefaultSampler() {
  return VideoFrameSampler::Create(MakeDefaultFrame());
}

TEST(GaussianFilteringTest, ShouldCrashWhenRowIsNegative) {
  EXPECT_DEATH(GetFilteredElement(*MakeDefaultSampler(),
                                  VideoFrameSampler::ChannelType::Y, -1,
                                  kDefaultColumn, kDefaultStdDev),
               _);
}

TEST(GaussianFilteringTest, ShouldCrashWhenRowIsOutOfRange) {
  EXPECT_DEATH(GetFilteredElement(*MakeDefaultSampler(),
                                  VideoFrameSampler::ChannelType::Y, 4,
                                  kDefaultColumn, kDefaultStdDev),
               _);
}

TEST(GaussianFilteringTest, ShouldCrashWhenColumnIsNegative) {
  EXPECT_DEATH(GetFilteredElement(*MakeDefaultSampler(),
                                  VideoFrameSampler::ChannelType::Y,
                                  kDefaultRow, -1, kDefaultStdDev),
               _);
}

TEST(GaussianFilteringTest, ShouldCrashWhenColumnIsOutOfRange) {
  EXPECT_DEATH(GetFilteredElement(*MakeDefaultSampler(),
                                  VideoFrameSampler::ChannelType::Y,
                                  kDefaultRow, 4, kDefaultStdDev),
               _);
}

TEST(GaussianFilteringTest, ShouldCrashWhenStdDevIsNegative) {
  EXPECT_DEATH(GetFilteredElement(*MakeDefaultSampler(),
                                  VideoFrameSampler::ChannelType::Y,
                                  kDefaultRow, kDefaultColumn, -1.0),
               _);
}

TEST(GaussianFilteringTest, RoundingErrorsShouldNotHappen) {
  // These values should force a rounding error.
  constexpr int kWidth = 128;
  constexpr int kHeight = 128;
  constexpr double kStdDev = 40;
  const std::vector<uint8_t> data(kWidth * kHeight, 255);
  std::unique_ptr<VideoFrameSampler> sampler =
      VideoFrameSampler::Create(MakeFrame(kWidth, kHeight, data));

  EXPECT_THAT(GetFilteredElement(*sampler, VideoFrameSampler::ChannelType::Y,
                                 kWidth / 2, kHeight / 2, kStdDev),
              255);
}

TEST(HaltonFrameSamplerTest, FrameIsNotSampledWhenTimestampsAreEqual) {
  HaltonFrameSampler halton_frame_sampler;

  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/0, /*num_samples=*/1),
      Not(IsEmpty()));
  EXPECT_DEATH(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/0, /*num_samples=*/1),
      _);
}

#endif  // GTEST_HAS_DEATH_TEST

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldReturnEmptyListGivenInvalidInputNoCoordinates) {
  EXPECT_THAT(
      GetSampleValuesForFrame(MakeDefaultFrame(), {}, kDefaultScaledWidth,
                              kDefaultScaledHeight, kDefaultStdDevGaussianBlur),
      IsEmpty());
}

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldReturnEmptyListGivenInvalidInputOutOfRangeCoordinates) {
  const std::vector<Coordinates> kSampleCoordinates = {
      {.row = 0.2, .column = 0.7},
      {.row = 0.5, .column = 1.0},
      {.row = 0.3, .column = 0.7},
      {.row = 0.8, .column = 0.4}};

  EXPECT_THAT(GetSampleValuesForFrame(MakeDefaultFrame(), kSampleCoordinates,
                                      kDefaultScaledWidth, kDefaultScaledHeight,
                                      kDefaultStdDevGaussianBlur),
              IsEmpty());
}

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldReturnEmptyListGivenInvalidInputWidthZero) {
  const std::vector<Coordinates> kDefaultSampleCoordinates =
      MakeDefaultSampleCoordinates();

  EXPECT_THAT(
      GetSampleValuesForFrame(MakeDefaultFrame(), kDefaultSampleCoordinates, 0,
                              kDefaultScaledHeight, kDefaultStdDevGaussianBlur),
      IsEmpty());
}

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldReturnEmptyListGivenInvalidInputHeightZero) {
  const std::vector<Coordinates> kDefaultSampleCoordinates =
      MakeDefaultSampleCoordinates();

  EXPECT_THAT(GetSampleValuesForFrame(
                  MakeDefaultFrame(), kDefaultSampleCoordinates,
                  kDefaultScaledWidth, 0, kDefaultStdDevGaussianBlur),
              IsEmpty());
}

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldReturnEmptyListGivenInvalidInputStdDevNegative) {
  const std::vector<Coordinates> kDefaultSampleCoordinates =
      MakeDefaultSampleCoordinates();

  EXPECT_THAT(
      GetSampleValuesForFrame(MakeDefaultFrame(), kDefaultSampleCoordinates,
                              kDefaultScaledWidth, kDefaultScaledHeight, -1.0),
      IsEmpty());
}

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldReturnEmptyListWhenUpscaling) {
  EXPECT_THAT(GetSampleValuesForFrame(MakeDefaultFrame(),
                                      MakeDefaultSampleCoordinates(),
                                      /*scaled_width=*/8, /*scaled_height=*/8,
                                      kDefaultStdDevGaussianBlur),
              IsEmpty());
}

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldReturnGivenValueWhenNoScalingOrFilteringIsDefined) {
  // 4x4 i420 frame data.
  const int kLumaWidth = 4;
  const int kLumaHeight = 4;
  const int kChromaWidth = 2;
  const uint8_t kYContent[16] = {20, 196, 250, 115, 139, 39, 99, 197,
                                 21, 166, 254, 28,  227, 54, 64, 46};
  const uint8_t kUContent[4] = {156, 203, 36, 128};
  const uint8_t kVContent[4] = {112, 2, 0, 24};
  const scoped_refptr<I420Buffer> kI420Buffer =
      I420Buffer::Copy(kLumaWidth, kLumaHeight, kYContent, kLumaWidth,
                       kUContent, kChromaWidth, kVContent, kChromaWidth);
  VideoFrame frame =
      VideoFrame::Builder().set_video_frame_buffer(kI420Buffer).build();

  // Coordinates in all planes.
  const std::vector<Coordinates> kSampleCoordinates = {
      {.row = 0.2, .column = 0.7},
      {.row = 0.5, .column = 0.9},
      {.row = 0.3, .column = 0.7},
      {.row = 0.8, .column = 0.4}};

  // No scaling.
  const int kScaledWidth = kLumaWidth;
  const int kScaledHeight = kLumaHeight;

  // No filtering.
  const double kStdDevGaussianBlur = 0.02;

  EXPECT_THAT(
      GetSampleValuesForFrame(frame, kSampleCoordinates, kScaledWidth,
                              kScaledHeight, kStdDevGaussianBlur),
      ElementsAre(AllOf(Field(&FilteredSample::value, DoubleEq(156.0)),
                        Field(&FilteredSample::plane, ImagePlane::kChroma)),
                  AllOf(Field(&FilteredSample::value, DoubleEq(2.0)),
                        Field(&FilteredSample::plane, ImagePlane::kChroma)),
                  AllOf(Field(&FilteredSample::value, DoubleEq(36.0)),
                        Field(&FilteredSample::plane, ImagePlane::kChroma)),
                  AllOf(Field(&FilteredSample::value, DoubleEq(64.0)),
                        Field(&FilteredSample::plane, ImagePlane::kLuma))));
}

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldScaleTheFrameWhenScalingIsRequested) {
  // 4x4 i420 frame data.
  const int kLumaWidth = 4;
  const int kLumaHeight = 4;
  const int kChromaWidth = 2;
  const uint8_t kYContent[16] = {20, 196, 250, 115, 139, 39, 99, 197,
                                 21, 166, 254, 28,  227, 54, 64, 46};
  const uint8_t kUContent[4] = {156, 203, 36, 128};
  const uint8_t kVContent[4] = {112, 2, 0, 24};
  const scoped_refptr<I420Buffer> kI420Buffer =
      I420Buffer::Copy(kLumaWidth, kLumaHeight, kYContent, kLumaWidth,
                       kUContent, kChromaWidth, kVContent, kChromaWidth);
  VideoFrame frame =
      VideoFrame::Builder().set_video_frame_buffer(kI420Buffer).build();

  // Coordinates in all planes.
  const std::vector<Coordinates> kSampleCoordinates = {
      {.row = 0.2, .column = 0.7},
      {.row = 0.5, .column = 0.9},
      {.row = 0.3, .column = 0.7},
      {.row = 0.8, .column = 0.4}};

  // With scaling.
  const int kScaledWidth = 2;
  const int kScaledHeight = 2;

  // No filtering.
  const double kStdDevGaussianBlur = 0.02;

  EXPECT_THAT(
      GetSampleValuesForFrame(frame, kSampleCoordinates, kScaledWidth,
                              kScaledHeight, kStdDevGaussianBlur),
      ElementsAre(AllOf(Field(&FilteredSample::value, DoubleEq(131.0)),
                        Field(&FilteredSample::plane, ImagePlane::kChroma)),
                  AllOf(Field(&FilteredSample::value, DoubleEq(35.0)),
                        Field(&FilteredSample::plane, ImagePlane::kChroma)),
                  AllOf(Field(&FilteredSample::value, DoubleEq(131.0)),
                        Field(&FilteredSample::plane, ImagePlane::kChroma)),
                  AllOf(Field(&FilteredSample::value, DoubleEq(98.0)),
                        Field(&FilteredSample::plane, ImagePlane::kLuma))));
}

TEST(HaltonFrameSamplerGaussianFilteringTest,
     ShouldReturnFilteredValuesWhenFilteringIsRequested) {
  // 8x8 i420 frame data.
  const int kLumaWidth = 8;
  const int kLumaHeight = 8;
  const int kChromaWidth = 4;
  const uint8_t kYContent[kLumaWidth * kLumaHeight] = {
      219, 38,  75,  13,  77,  22,  108, 5,    //
      199, 105, 237, 3,   194, 63,  200, 95,   //
      116, 21,  224, 21,  79,  210, 138, 3,    //
      130, 156, 139, 176, 1,   134, 191, 61,   //
      123, 59,  34,  237, 223, 162, 113, 108,  //
      146, 210, 214, 110, 50,  205, 135, 18,   //
      51,  198, 63,  69,  70,  117, 180, 126,  //
      244, 250, 194, 195, 85,  24,  25,  224};
  const uint8_t kUContent[16] = {
      219, 38,  75,  13, 77,  22, 108, 5,  //
      199, 105, 237, 3,  194, 63, 200, 95,
  };
  const uint8_t kVContent[16] = {
      123, 59,  34, 237, 223, 162, 113, 108,  //
      51,  198, 63, 69,  70,  117, 180, 126,
  };
  const scoped_refptr<I420Buffer> kI420Buffer =
      I420Buffer::Copy(kLumaWidth, kLumaHeight, kYContent, kLumaWidth,
                       kUContent, kChromaWidth, kVContent, kChromaWidth);
  VideoFrame frame =
      VideoFrame::Builder().set_video_frame_buffer(kI420Buffer).build();

  // Coordinates in all (YUV) planes.
  const std::vector<Coordinates> kSampleCoordinates = {
      {.row = 0.2, .column = 0.7},
      {.row = 0.5, .column = 0.9},
      {.row = 0.3, .column = 0.7},
      {.row = 0.8, .column = 0.4}};

  // No scaling.
  const int kScaledWidth = kLumaWidth;
  const int kScaledHeight = kLumaHeight;

  // With filtering (kernel size 3x3 minimum required).
  const double kStdDevGaussianBlur = 1;

  EXPECT_THAT(
      GetSampleValuesForFrame(frame, kSampleCoordinates, kScaledWidth,
                              kScaledHeight, kStdDevGaussianBlur),
      ElementsAre(
          AllOf(Field(&FilteredSample::value, DoubleEq(114.6804322931639)),
                Field(&FilteredSample::plane, ImagePlane::kChroma)),
          AllOf(Field(&FilteredSample::value, DoubleEq(109.66816384377159)),
                Field(&FilteredSample::plane, ImagePlane::kChroma)),
          AllOf(Field(&FilteredSample::value, DoubleEq(133.7339472739954)),
                Field(&FilteredSample::plane, ImagePlane::kChroma)),
          AllOf(Field(&FilteredSample::value, DoubleEq(104.43135638243807)),
                Field(&FilteredSample::plane, ImagePlane::kLuma))));
}

TEST(HaltonFrameSamplerTest, CoordinatesFollowsHaltonSequence) {
  HaltonFrameSampler halton_frame_sampler;
  const int kNumSamples = 1;
  EXPECT_THAT(halton_frame_sampler.GetSampleCoordinatesForFrame(kNumSamples),
              ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(0.0)),
                                Field(&Coordinates::column, DoubleEq(0.0)))));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrame(kNumSamples),
      ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(1.0 / 2)),
                        Field(&Coordinates::column, DoubleEq(1.0 / 3)))));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrame(kNumSamples),
      ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(1.0 / 4)),
                        Field(&Coordinates::column, DoubleEq(2.0 / 3)))));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrame(kNumSamples),
      ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(3.0 / 4)),
                        Field(&Coordinates::column, DoubleEq(1.0 / 9)))));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrame(kNumSamples),
      ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(1.0 / 8)),
                        Field(&Coordinates::column, DoubleEq(4.0 / 9)))));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrame(kNumSamples),
      ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(5.0 / 8)),
                        Field(&Coordinates::column, DoubleEq(7.0 / 9)))));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrame(kNumSamples),
      ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(3.0 / 8)),
                        Field(&Coordinates::column, DoubleEq(2.0 / 9)))));
}

TEST(HaltonFrameSamplerTest, GeneratesMultipleSamplesWhenRequested) {
  HaltonFrameSampler halton_frame_sampler;
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrame(3),
      ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(0.0)),
                        Field(&Coordinates::column, DoubleEq(0.0))),
                  AllOf(Field(&Coordinates::row, DoubleEq(1.0 / 2)),
                        Field(&Coordinates::column, DoubleEq(1.0 / 3))),
                  AllOf(Field(&Coordinates::row, DoubleEq(1.0 / 4)),
                        Field(&Coordinates::column, DoubleEq(2.0 / 3)))));
}

TEST(HaltonFrameSamplerTest, ShouldChangeIndexWhenRequestedTo) {
  HaltonFrameSampler halton_frame_sampler;
  halton_frame_sampler.SetCurrentIndex(1);
  EXPECT_EQ(halton_frame_sampler.GetCurrentIndex(), 1);
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrame(1),
      ElementsAre(AllOf(Field(&Coordinates::row, DoubleEq(1.0 / 2)),
                        Field(&Coordinates::column, DoubleEq(1.0 / 3)))));
}

TEST(HaltonFrameSamplerTest, FirstFrameIsSampled) {
  HaltonFrameSampler halton_frame_sampler;
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/0, /*num_samples=*/1),
      Not(IsEmpty()));
}

TEST(HaltonFrameSamplerTest,
     DeltaFrameFollowingSampledFrameWithTooShortTimeDeltaIsNotSampled) {
  HaltonFrameSampler halton_frame_sampler;
  halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
      /*is_key_frame=*/false, /*rtp_timestamp=*/0, /*num_samples=*/1);
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/1, /*num_samples=*/1),
      IsEmpty());
}

TEST(HaltonFrameSamplerTest,
     DeltaFramesAreSampledBasedOnHowManyFramesHasPassedSinceLastSampledFrame) {
  HaltonFrameSampler halton_frame_sampler;
  uint32_t rtp_timestamp = 0;
  const int kNumSamples = 1;

  // The number of frames between each sample is defined as
  //   33 - mod(number_of_sampled_frames, 8)
  // so the following gets get coverage for [26, 33] two times.
  for (int iterations = 0; iterations < 2; ++iterations) {
    for (int num_sampled_frames = 0; num_sampled_frames < 8;
         ++num_sampled_frames) {
      EXPECT_THAT(halton_frame_sampler
                      .GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
                          /*is_key_frame=*/false, rtp_timestamp, kNumSamples),
                  Not(IsEmpty()));
      ++rtp_timestamp;
      for (int num_unsampled_frames = 1;
           num_unsampled_frames < 33 - num_sampled_frames;
           ++num_unsampled_frames) {
        EXPECT_THAT(halton_frame_sampler
                        .GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
                            /*is_key_frame=*/false, rtp_timestamp, kNumSamples),
                    IsEmpty());
        ++rtp_timestamp;
      }
    }
  }
}

TEST(HaltonFrameSamplerTest, KeyFrameIsSampled) {
  HaltonFrameSampler halton_frame_sampler;
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/0, /*num_samples=*/1),
      Not(IsEmpty()));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/true, /*rtp_timestamp=*/1, /*num_samples=*/1),
      Not(IsEmpty()));
}

TEST(HaltonFrameSamplerTest,
     SampleFramesWhenEnoughTimeHasPassedSinceLastSampledFrame) {
  HaltonFrameSampler halton_frame_sampler;
  const uint32_t kRtpTimestamp = 0;
  const int kNumSamples = 1;
  const uint32_t kSufficientDuration = 90'000;
  const uint32_t kTooShortDuration = 1;
  halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
      /*is_key_frame=*/false, kRtpTimestamp, kNumSamples);
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, kRtpTimestamp + kSufficientDuration,
          kNumSamples),
      Not(IsEmpty()));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false,
          kRtpTimestamp + kSufficientDuration + kTooShortDuration, kNumSamples),
      IsEmpty());
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, kRtpTimestamp + 2 * kSufficientDuration,
          kNumSamples),
      Not(IsEmpty()));
}

TEST(HaltonFrameSamplerTest,
     FrameIsNotSampledWhenTooShortTimeHasPassedSinceLastSampledFrame) {
  HaltonFrameSampler halton_frame_sampler;
  const uint32_t kRtpTimestamp = 0;
  const uint32_t kTooShortDuration = 90'000 - 1;
  halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
      /*is_key_frame=*/false, kRtpTimestamp, /*num_samples=*/1);
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, kRtpTimestamp + kTooShortDuration,
          /*num_samples=*/1),
      IsEmpty());
}

TEST(HaltonFrameSamplerTest,
     SampleFramesWhenEnoughTimeWithWraparoundHasPassedSinceLastSampledFrame) {
  HaltonFrameSampler halton_frame_sampler;

  // Time delta = 90'000.
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/0xFFFE'A071,
          /*num_samples=*/1),
      Not(IsEmpty()));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/1, /*num_samples=*/1),
      Not(IsEmpty()));
}

TEST(
    HaltonFrameSamplerTest,
    FrameIsNotSampledWhenTooShortTimeDeltaWithWraparoundSinceLastSampledFrame) {
  HaltonFrameSampler halton_frame_sampler;

  // Time delta = 89'999.
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/0xFFFE'A072,
          /*num_samples=*/1),
      Not(IsEmpty()));
  EXPECT_THAT(
      halton_frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
          /*is_key_frame=*/false, /*rtp_timestamp=*/1, /*num_samples=*/1),
      IsEmpty());
}

}  // namespace
}  // namespace webrtc
